[Chapter 24] 24.3 Write a Delivery Agent Script
Author: Bryan Costales & Eric Allman
Date: 1999-01-06T19:38:38Z

#

| Chapter 24 Aliases |

## 24.3 Write a Delivery Agent Script

The program that is driven by the `prog` delivery agent may be a compiled executable binary, a shell script, or even a perl(1) script. The limitation on the kind of program that may be run is made by the sh(1) shell (if `sh -c` is used in the `A=`) or by execve(2) (if it is launched directly from the `P=`). You need to read the manuals on your system to determine your limitations. For example, not all versions of sh(1) allow constructs like the following in scripts:

>     #!/usr/local/bin/perl

When this appears as the first line of a script, the `#!` tells sh(1) or execve(2) to run the program whose pathname follows, to execute the commands in the script. [6]

> [6] Not all versions of UNIX support this feature, and on some of those that do support it, only a few shells are supported.

In writing a program for mail delivery using the `prog` delivery agent, some unexpected problems can arise. We will illustrate, using fragments from a Bourne shell script.

### 24.3.1 Duplicates discarded

When sendmail gathers its list of recipients, it views a program to run as just another recipient. Before performing any delivery, it sorts the list of recipients and discards any duplicates. Ordinarily, this is just the behavior that is desired, but discarding duplicate programs from the aliases(5) file [7] can cause some users to lose mail. To illustrate, consider a program that notifies the system administrator that mail has arrived for a retired user:

> [7] Under V8 sendmail this is no longer a problem for duplicate programs listed in ~/.forward files (see Section 25.7.4) but still is a problem for aliases. The solution that sendmail uses is to internally append the uid of the ~/.forward file's owner to the program name, thus making the program name more unique.

>     #!/bin/sh
>     /usr/ucb/mail -s gone postmaster

This script reads everything (the mail message) from its standard input and feeds what it reads to the /usr/ucb/mail program. The command-line arguments to mail are a subject line of `gone` and a recipient of `postmaster`. Now consider two aliases that use this program:

>     george: "|/usr/local/bin/gone"
>     ben:    "|/usr/local/bin/gone"

When mail is sent to both `george` and `ben`, sendmail aliases each to the program `|/usr/local/bin/gone`. But since both the two addresses are identical, sendmail discards one.

To avoid this problem (which is most common in user ~/.forward files), design all delivery programs to require at least one unique argument. For example, the above program should be rewritten to require the user's name as an argument:

>     #!/bin/sh
>     if [ ${#} -ne 2 ]; then
>             echo $0 needs a user name.
>             exit
>     fi
>     /usr/ucb/mail -s "$1 gone" postmaster

By requiring a username as an argument, the once-faulty aliases are made unique:

>     george: "|/usr/local/bin/gone george"
>     ben:    "|/usr/local/bin/gone ben"

Although the program paths are still the same, the addresses (name and arguments together) are different, and neither is discarded.

### 24.3.2 Correct exit(2) values

The sendmail program expects its `A=` programs to exit with reasonable exit(2) values. The values that it expects are listed in <sysexits.h>. Exiting with unexpected values causes sendmail to bounce mail and give an unclear message:

>     554 Unknown status val

Here, `val` is the unexpected error value. To illustrate, consider the following rewrite of the previous script:

>     #!/bin/sh
>     EX_OK=0                   # From <sysexits.h>
>     EX_USAGE=64               # From <sysexits.h>
>     if [ ${#} -ne 2 ]; then
>             echo $0 needs a user name.
>             exit $EX_USAGE
>     fi
>     /usr/ucb/mail -s "$1 gone" postmaster
>     exit $EX_OK

Here, if the argument count is wrong, we exit with the value EX_USAGE, thus producing a clearer (two-line) error message:

>     /usr/local/bin/gone needs a user name.
>     /usr/local/bin/gone... Bad usage.

If all goes well, we then exit with EX_OK so that sendmail knows that the mail was successfully delivered.

### 24.3.3 Is It Really EX_OK?

When sendmail sees that the `A=` program exited with EX_OK, it assumes that the mail message was successfully delivered. It is vital for programs that deliver mail to exit with EX_OK only if delivery was 100% successful. Failure to take precautions to detect every possible error can result in lost mail and angry users. To illustrate, consider the following common C language statement:

>     (void)fclose(fp);

If the file that is being written to is remotely mounted, the written data may be cached locally. All the preceding write statements will have succeeded, but if the remote host crashes after the last write (but before the close), some of the data can be lost. The fclose(3) fails, but the `(void)` prevents detection of that failure.

Even in writing small shell scripts, it is important to include error checking. The following rewrite of our gone program includes error checking but does not handle signals. We leave that as an exercise for the reader:

>     #!/bin/sh
>     EX_OK=0                   # From <sysexits.h>
>     EX_USAGE=64               # From <sysexits.h>
>     EX_SOFTWARE=70            # From <sysexits.h>
>     if [ ${#} -ne 2 ]; then
>             echo $0 needs a user name.
>             exit $EX_USAGE
>     fi
>     if /usr/ucb/mail -s "$1 gone" postmaster >/dev/null 2>&1
>     then
>             exit $EX_OK
>     fi
>     exit $EX_SOFTWARE

24.2 Forms of Alias Delivery |  | 24.4 Special Aliases